/*
 * Copyright (c) 2024 Shenzhen Kaihong Digital Industry Development Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <mutex>
#include <memory>
#include "kh_sle_connect.h"
#include "kh_sle_host.h"
#include "sle_connect_interface.h"
#include "sle_type_trans_utils.h"
#include "sle_hilog.h"

#ifdef __cplusplus
extern "C" {
#endif

namespace OHOS {
namespace NearLink {
namespace SleStandard {
#define SLE_MAX_CONNECT_CALLBACK_NUM  3

static SleConnectManager *g_connectManagerInstance;
static SleConnectCallbacks* g_connectCallbacks[SLE_MAX_CONNECT_CALLBACK_NUM] = {nullptr};
static std::mutex g_connectCbLock;
static bool g_isConnectInited = false;

class SleConnectCallbackWapper : public SleConnectManagerCallback {
public:
    SleConnectCallbackWapper(){};

    void OnConnectStateChange(uint16_t connectId, const SleDeviceAddress &address, SleConnectState connState,
        SlePairState pairState, SleDisConnectReason reason) override
    {
        SLE_LOGD("OnConnectStateChange connectId(%d) SleConnectState(%d) SlePairState(%d) SleDisConnectReason(%d)",
            connectId, connState, pairState, reason);
        std::lock_guard<std::mutex> lock(g_connectCbLock);
        for (int i = 0; i < SLE_MAX_CONNECT_CALLBACK_NUM; i++) {
            if (g_connectCallbacks[i] != nullptr && g_connectCallbacks[i]->OnSleConnStateChangeCb != nullptr) {
                g_connectCallbacks[i]->OnSleConnStateChangeCb(connectId, &address, connState, pairState, reason);
            }
        }
    }

    void OnConnectParamUpdateComplete(
        uint16_t connectId, const SleConnectionParamUpdateEvt &param, SleErrorCode errCode) override
    {
        SLE_LOGD("OnConnectParamUpdateComplete connectId(%d) errCode(%d)", connectId, errCode);
        std::lock_guard<std::mutex> lock(g_connectCbLock);
        for (int i = 0; i < SLE_MAX_CONNECT_CALLBACK_NUM; i++) {
            if (g_connectCallbacks[i] != nullptr && g_connectCallbacks[i]->OnSleConnParamUpdatedCb != nullptr) {
                g_connectCallbacks[i]->OnSleConnParamUpdatedCb(connectId, errCode, &param);
            }
        }
    }

    void OnAuthComplete(
        uint16_t connectId, const SleDeviceAddress &address, const SleAuthInfo &info, SleErrorCode errCode) override
    {
        SLE_LOGD("OnAuthComplete connectId(%d) errCode(%d)", connectId, errCode);
    }

    void OnPairComplete(uint16_t connectId, const SleDeviceAddress &address, SleErrorCode errCode) override
    {
        SLE_LOGD("OnPairComplete connectId(%d) errCode(%d)", connectId, errCode);
        SlePairState pairState = (errCode == SLE_NO_ERROR) ? SLE_PAIR_PAIRED_STATE : SLE_PAIR_ERROR_STATE;
        OnConnectStateChange(
            connectId, address, SLE_CONNECT_CONNECTED_STATE, pairState, SLE_CONNECT_OR_DISCONNECT_SUCCESS);
    }
};

static std::shared_ptr<SleConnectCallbackWapper> g_connnectCbWapper;

SleErrorCode CheckConnectOperation()
{
    SLE_ASSERT_AND_RETURN_RET_LOG(g_isConnectInited == true, SLE_ERR_INTERNAL_WRONG, "connect service not inited");
    SLE_ASSERT_AND_RETURN_RET_LOG(g_connectManagerInstance, SLE_ERR_INTERNAL_WRONG, "connect instance null");
    return SLE_NO_ERROR;
}

SleErrorCode InitSleConnectService()
{
    SLE_LOGI("InitSleConnectService In");
    SLE_ASSERT_AND_RETURN_RET_LOG(g_isConnectInited == false, SLE_NO_ERROR, "connect already inited error!");
    g_connnectCbWapper = std::make_shared<SleConnectCallbackWapper>();
    SLE_ASSERT_AND_RETURN_RET_LOG(g_connnectCbWapper, SLE_ERR_FAILED, "callback wapper error!");
    if (g_connectManagerInstance == nullptr) {
        g_connectManagerInstance = new SleConnectManager();
    }
    SLE_ASSERT_AND_RETURN_RET_LOG(g_connectManagerInstance, SLE_ERR_FAILED, "connect manager instance error!");
    SleErrorCode ret = g_connectManagerInstance->RegisterConnectCallback(g_connnectCbWapper);
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, ret, "init connect service failed!");
    g_isConnectInited = true;
    return SLE_NO_ERROR;
}

SleErrorCode DeinitSleConnectService()
{
    SLE_LOGI("DeinitSleConnectService In");
    SLE_ASSERT_AND_RETURN_RET_LOG(g_isConnectInited == true, SLE_NO_ERROR, "connect not inited error!");
    SLE_ASSERT_AND_RETURN_RET_LOG(g_connnectCbWapper, SLE_ERR_FAILED, "callback wapper error!");
    SleErrorCode ret = g_connectManagerInstance->UnregisterConnectCallback(g_connnectCbWapper);
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, ret, "deinit connect service failed!");
    if (g_connectManagerInstance != nullptr) {
        delete g_connectManagerInstance;
        g_connectManagerInstance = nullptr;
    }
    g_isConnectInited = false;
    return SLE_NO_ERROR;
}

SleErrorCode SleConnect(const SleDeviceAddress *remoteAddr)
{
    SLE_ASSERT_AND_RETURN_RET_LOG(remoteAddr, SLE_ERR_INVALID_PARAM, "param remote address error!");
    SLE_LOGI("SleConnect " MACSTRPRI, MAC2STRPRI(remoteAddr->addr));
    SleErrorCode ret = CheckConnectOperation();
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, SLE_ERR_CHECKOUT_FAILED, "check operation failed!");
    ret = g_connectManagerInstance->Connect(*remoteAddr);
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, ret, "connect failed!");
    return SLE_NO_ERROR;
}

SleErrorCode SleDisconnect(const SleDeviceAddress *remoteAddr)
{
    SLE_ASSERT_AND_RETURN_RET_LOG(remoteAddr, SLE_ERR_INVALID_PARAM, "param remote address error!");
    SLE_LOGI("SleDisconnect " MACSTRPRI, MAC2STRPRI(remoteAddr->addr));
    SleErrorCode ret = CheckConnectOperation();
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, SLE_ERR_CHECKOUT_FAILED, "check operation failed!");
    ret = g_connectManagerInstance->Disconnect(*remoteAddr);
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, ret, "connect failed!");
    return SLE_NO_ERROR;
}

SleErrorCode SleUpdateConnParam(const SleConnectionParamUpdate *param)
{
    SLE_ASSERT_AND_RETURN_RET_LOG(param, SLE_ERR_INVALID_PARAM, "param connection params error!");
    SleErrorCode ret = CheckConnectOperation();
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, SLE_ERR_CHECKOUT_FAILED, "check operation failed!");
    SleConnectParam params;
    params.intervalMin = param->intervalMin;
    params.intervalMax = param->intervalMax;
    params.latency = param->maxLatency;
    params.supervisionTimeout = param->supervisionTimeout;
    SLE_LOGI("SleUpdateConnParam connectId(%d)", param->connectId);
    ret = g_connectManagerInstance->UpdateConnectParam(param->connectId, params);
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, ret, "update connect param failed!");
    return SLE_NO_ERROR;
}

SleErrorCode SleUpdateAllConnParam(SleConnectDev *successUpdateDevList, uint16_t *successUpdateNum)
{
    SleErrorCode ret = CheckConnectOperation();
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, SLE_ERR_CHECKOUT_FAILED, "check operation failed!");
    std::vector<SleConnectDev> devices;
    uint16_t number = 0;
    ret = g_connectManagerInstance->UpdateAllConnectParam(devices, number);
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, ret, "update connect param failed!");
    *successUpdateNum = number;

    for (int i = 0; i < number; ++i) {
        successUpdateDevList[i].connId = devices.at(i).connId;
        successUpdateDevList[i].addr.addrType = devices.at(i).addr.addrType;
        memcpy(successUpdateDevList[i].addr.addr, devices.at(i).addr.addr, SLE_DEVICE_ADDRESS_LEN);
    }
    return SLE_NO_ERROR;
}

SleErrorCode SleGetRecomSendDataSize(uint16_t connectId, uint32_t *recomSendDataSize)
{
    SleErrorCode ret = CheckConnectOperation();
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, SLE_ERR_CHECKOUT_FAILED, "check operation failed!");
    uint32_t sendDataSize = 0;
    ret = g_connectManagerInstance->GetRecomSendDataSize(connectId, sendDataSize);
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, ret, "update connect param failed!");
    *recomSendDataSize = sendDataSize;
    return SLE_NO_ERROR;
}

SleErrorCode SendPairReq(const SleDeviceAddress *remoteAddr)
{
    SLE_ASSERT_AND_RETURN_RET_LOG(remoteAddr, SLE_ERR_INVALID_PARAM, "param remote address error!");
    SLE_LOGI("SendPairReq " MACSTRPRI, MAC2STRPRI(remoteAddr->addr));
    SleErrorCode ret = CheckConnectOperation();
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, SLE_ERR_CHECKOUT_FAILED, "check operation failed!");
    ret = g_connectManagerInstance->SendPairRequest(*remoteAddr);
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, ret, "send pair request failed!");
    return SLE_NO_ERROR;
}

SleErrorCode RemovePair(const SleDeviceAddress *remoteAddr)
{
    SLE_ASSERT_AND_RETURN_RET_LOG(remoteAddr, SLE_ERR_INVALID_PARAM, "param remote address error!");
    SLE_LOGI("RemovePair " MACSTRPRI, MAC2STRPRI(remoteAddr->addr));
    SleErrorCode ret = CheckConnectOperation();
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, SLE_ERR_CHECKOUT_FAILED, "check operation failed!");
    ret = g_connectManagerInstance->RemovePairedDevice(*remoteAddr);
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, ret, "remove paired device failed!");
    return SLE_NO_ERROR;
}

SleErrorCode RemoveAllPairs()
{
    SLE_LOGI("RemoveAllPairs In");
    SleErrorCode ret = CheckConnectOperation();
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, SLE_ERR_CHECKOUT_FAILED, "check operation failed!");
    ret = g_connectManagerInstance->RemoveAllPairedDevices();
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, ret, "remove all paired devices failed!");
    return SLE_NO_ERROR;
}

SleErrorCode GetDevicePairState(const SleDeviceAddress *addr, SlePairState *pairState)
{
    SLE_ASSERT_AND_RETURN_RET_LOG(addr, SLE_ERR_INVALID_PARAM, "param remote address error!");
    SLE_LOGI("GetDevicePairState " MACSTRPRI, MAC2STRPRI(addr->addr));
    SleErrorCode ret = CheckConnectOperation();
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, SLE_ERR_CHECKOUT_FAILED, "check operation failed!");
    ret = g_connectManagerInstance->GetPairState(*addr, *pairState);
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, ret, "get pair state failed!");
    return SLE_NO_ERROR;
}

SleErrorCode ReadRemoteRssi(uint16_t connectId, int8_t *rssi)
{
    SLE_LOGD("ReadRemoteRssi connectId(%d)", connectId);
    SleErrorCode ret = CheckConnectOperation();
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, SLE_ERR_CHECKOUT_FAILED, "check operation failed!");
    ret = g_connectManagerInstance->ReadRemoteRssi(connectId, *rssi);
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, ret, "read rssi failed!");
    return SLE_NO_ERROR;
}

SleErrorCode GetConnectDevList(SleConnectDev *connectDevList, uint16_t *connectNum)
{
    SLE_LOGI("GetConnectDevList inputNum(%d)", *connectNum);
    SleErrorCode ret = CheckConnectOperation();
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, SLE_ERR_CHECKOUT_FAILED, "check operation failed!");
    std::vector<SleConnectDev> devices;
    uint16_t number;
    ret = g_connectManagerInstance->GetConnectDeviceList(devices, number);
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, ret, "get connect device list failed!");
    if (*connectNum > number) {
        *connectNum = number;
    }
    for (int i = 0; i < *connectNum; ++i) {
        connectDevList[i].connId = devices.at(i).connId;
        connectDevList[i].addr.addrType = devices.at(i).addr.addrType;
        memcpy(connectDevList[i].addr.addr, devices.at(i).addr.addr, SLE_DEVICE_ADDRESS_LEN);
    }
    return SLE_NO_ERROR;
}

SleErrorCode GetConnectDeviceId(const SleDeviceAddress *remoteDeviceAddr, uint16_t *connectId)
{
    SLE_ASSERT_AND_RETURN_RET_LOG(remoteDeviceAddr, SLE_ERR_INVALID_PARAM, "param remote address error!");
    SleErrorCode ret = CheckConnectOperation();
    SLE_LOGI("GetConnectDeviceId " MACSTRPRI, MAC2STRPRI(remoteDeviceAddr->addr));
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, SLE_ERR_CHECKOUT_FAILED, "check operation failed!");
    ret = g_connectManagerInstance->GetConnectId(*remoteDeviceAddr, *connectId);
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, ret, "get connected device id failed!");
    return SLE_NO_ERROR;
}

SleErrorCode CheckIsConnected(uint16_t connectId)
{
    SLE_LOGI("CheckIsConnected connectId(%d)", connectId);
    SleConnectDev connectDevList[SLE_MAX_CONNECT_ID_NUM];
    uint16_t number = 0;
    GetConnectDevList(connectDevList, &number);
    for(int i = 0; i < SLE_MAX_CONNECT_ID_NUM; i++) {
        if (connectId == connectDevList[i].connId) {
            SLE_LOGD("connectId is connected");
            return SLE_NO_ERROR;
        }
    }
    return SLE_ERR_INVALID_PARAM;
}

SleErrorCode RegisterConnectCallbacks(SleConnectCallbacks *connectCallback)
{
    SLE_LOGI("RegisterConnectCallbacks In");
    SLE_ASSERT_AND_RETURN_RET_LOG(connectCallback, SLE_ERR_INVALID_PARAM, "connect callback error!");
    SleErrorCode ret = CheckConnectOperation();
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, SLE_ERR_CHECKOUT_FAILED, "check operation failed!");
    std::lock_guard<std::mutex> lock(g_connectCbLock);
    for(int i = 0; i < SLE_MAX_CONNECT_CALLBACK_NUM; i++) {
        SLE_ASSERT_AND_RETURN_RET_LOG(
            g_connectCallbacks[i] != connectCallback, SLE_ERR_INVALID_PARAM, "connect callback has registered!");
    }
    ret = SLE_ERR_FAILED;
    for(int i = 0; i < SLE_MAX_CONNECT_CALLBACK_NUM; i++) {
        if (g_connectCallbacks[i] == nullptr) {
            g_connectCallbacks[i] = connectCallback;
            ret = SLE_NO_ERROR;
            break;
        }
    }
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, ret, "register announce callback failed!");
    return SLE_NO_ERROR;
}

SleErrorCode UnregisterConnectCallbacks(SleConnectCallbacks *connectCallback)
{
    SLE_LOGI("UnregisterConnectCallbacks In");
    SLE_ASSERT_AND_RETURN_RET_LOG(connectCallback, SLE_ERR_INVALID_PARAM, "connect callback error!");
    SleErrorCode ret = CheckConnectOperation();
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, SLE_ERR_CHECKOUT_FAILED, "check operation failed!");
    bool registered = false;
    std::lock_guard<std::mutex> lock(g_connectCbLock);
    for(int i = 0; i < SLE_MAX_CONNECT_CALLBACK_NUM; i++) {
        if (g_connectCallbacks[i] == connectCallback) {
            g_connectCallbacks[i] = nullptr;
            registered = true;
            break;
        }
    }
    SLE_ASSERT_AND_RETURN_RET_LOG(registered == true, SLE_ERR_INVALID_PARAM, "connect callback has not registered!");
    return SLE_NO_ERROR;
}
} // SleStandard
} // NearLink
} // OHOS

#ifdef __cplusplus
}
#endif